﻿// ------------------------------------------------------------------------
// 项目名称：Canroc.Net 
// 版权归属：Canroc（https://gitee.com/canroc）
//
// 许可证信息
// Canroc.Net项目主要遵循 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-APACHE 文件。
//
// 使用条款：
// 1.使用本项目应遵守相关法律法规和许可证的要求。
// 2.不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动。
// 3.任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任
//
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
//
// 其他重要信息
// Canroc.Net 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// ------------------------------------------------------------------------

using Canroc.Net.SystemService.RedisService;
using Microsoft.AspNetCore.Http.Features;

namespace Canroc.Net.Web.Core.Middleware;

/// <summary>
///     校验签名中间件
/// </summary>
public class CheckSignMiddleware(IRedisService redisService, RequestDelegate next)
{
    /// <summary>
    ///     中间件执行方法ß
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public async Task InvokeAsync(HttpContext context)
    {
        // 判断请求是否为/api开头
        if (!context.Request.Path.StartsWithSegments("/api"))
        {
            // 执行下一个中间件
            await next(context);
            return;
        }

        // 判断请求是否为Swagger请求的
        if (string.Equals("swagger", context.Request.Headers["request-from"]))
        {
            // 执行下一个中间件
            await next(context);
            return;
        }

        // 判断目标路由是否需要签名
        var endpointFeature = context.Features.Get<IEndpointFeature>();
        var noSignAttribute = endpointFeature?.Endpoint?.Metadata.GetMetadata<NoSignAttribute>();
        if (noSignAttribute is not null && noSignAttribute.GetValidate())
        {
            // 执行下一个中间件
            await next(context);
            return;
        }

        try
        {
            // 读取请求头
            var headers = context.Request.Headers;
            var requestId = headers["X-Request-Id"].ToString();
            var timestamp = headers["X-Timestamp"].ToString();
            var signature = headers["X-Signature"].ToString();
            // 验证请求头是否存在
            if (requestId.IsNullOrEmpty() || timestamp.IsNullOrEmpty() || signature.IsNullOrEmpty())
            {
                context.Response.StatusCode = (int)HttpStatusCodeEnum.BadRequest;
                await context.Response.WriteAsJsonAsync(
                    RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.BadRequest, "请求头缺失！"));
                return;
            }

            // 验证时间戳超过当前时间1天 视为无效请求
            if (DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(timestamp)) < DateTimeOffset.Now.AddDays(-1))
            {
                context.Response.StatusCode = (int)HttpStatusCodeEnum.BadRequest;
                await context.Response.WriteAsJsonAsync(
                    RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.BadRequest, "请求已过期！"));
                return;
            }

            // 验证此请求是否在redis中
            if (!await redisService.SortedSetAddAsync("request", requestId, double.Parse(timestamp)))
            {
                context.Response.StatusCode = (int)HttpStatusCodeEnum.BadRequest;
                await context.Response.WriteAsJsonAsync(
                    RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.BadRequest, "重复请求！"));
                return;
            }

            // 读取请求类型
            var method = context.Request.Method;
            var url = context.Request.Path.ToString() + context.Request.QueryString;
            var body = await GetRequestBody(context);

            // 验证签名
            var signStr = $"{method}\n{url}\n{requestId}\n{timestamp}\n{body}";
            if (!signStr.Sm3Verify(signature))
            {
                context.Response.StatusCode = (int)HttpStatusCodeEnum.BadRequest;
                await context.Response.WriteAsJsonAsync(
                    RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.BadRequest, "签名验证失败！"));
                return;
            }

            // 执行下一个中间件
            await next(context);
        }
        catch (Exception ex)
        {
            context.Response.StatusCode = (int)HttpStatusCodeEnum.InternalServerError;
            await context.Response.WriteAsJsonAsync(
                RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.InternalServerError, ex.Message));
        }
    }

    /// <summary>
    ///     获取请求Body
    /// </summary>
    /// <param name="context">请求上下文</param>
    /// <returns></returns>
    private static async Task<string> GetRequestBody(HttpContext context)
    {
        var body = "";
        if (!context.Request.Body.CanRead)
        {
            return body;
        }

        if (context.Request.HasFormContentType)
        {
            var form = await context.Request.ReadFormAsync();

            // 队列排序
            SortedDictionary<string, string> newParameters = new();
            foreach (var key in form.Keys)
            {
                if (!form.TryGetValue(key, out var value))
                {
                    continue;
                }

                newParameters.Add(key, value.ToString());
            }

            // 文件处理
            if (form.Files.Count > 0)
            {
                var file = form.Files[0];
                await using var fileStream = file.OpenReadStream();
                var fileBytes = new byte[file.Length];
                _ = await fileStream.ReadAsync(fileBytes.AsMemory(0, (int)file.Length));
                newParameters.Add("file", MD5Encryption.Encrypt(fileBytes));
            }

            // 拼接参数
            var parameterStr =
                newParameters.Aggregate("", (current, item) => current + $"&{item.Key}={item.Value}");
            body = parameterStr.Length > 1 ? MD5Encryption.Encrypt(parameterStr[1..]) : "";
        }
        else
        {
            var dataJson = await context.Request.ReadBodyContentAsync();
            body = string.IsNullOrEmpty(dataJson) ? "" : MD5Encryption.Encrypt(dataJson);
        }

        return body;
    }
}